iT邦幫忙

2025 iThome 鐵人賽

DAY 5
0

前言

到了第五天終於把前面開頭幾個限流算法介紹並實作完了,其實實作到一半時有點心虛,覺得是不是應該要把重點放在系統架構的設計,而不是太 Detail 的算法邏輯,但寫都寫了,而且也在過程中認識了那些從前沒有花時間搞清楚的用法、資料結構等等,例如 ConcurrentHashMap、Atomic 的整數類,以及為何要在訪問 Bucket 時使用 synchronized,當然我沒有深挖其中的底層邏輯,但還是建立了對他們高層次的理解,了解了它們的特性,下次再碰到時已經可以知道其出現背後的原因了。接下來幾篇的文章將會把視角拉高、擴大,開始考慮如何在分散式、微服務的環境下也能把限流器使用得當,設計出乾淨、健康的後端服務。

實作前導:RequestContext 基礎信息管理

在現代微服務架構下,一個看似簡單的用戶請求往往需要牽涉到多個模組、不同服務才能完成,就以我最近參與的專案來說,單單一顆小按鈕就需要經過 3 個後端服務,每個服務都需要知道這個請求「屬於哪個用戶」、「從哪個 IP 發起」等等的相關基本信息,最直觀的做法是方法需要什麼信息就直接作為參數傳遞進去,但這會有點問題:

  1. 方法參數會太多,簡潔的業務邏輯會被大量的 RequestContext 相關參數淹沒。
  2. 承上,如此會造成維護成本增加,要增加參數時改動範圍大,容易遺漏、出錯等等。

這時如果在請求進入到 Controller 以前,就將請求的基本信息統一提取出來,整理好包成一個物件,業務邏輯有需要調用相關信息時再引入該物件取得數據即可,當流程中有需要訪問其他微服務時,也可以很輕鬆地將這個基本信息放在 Header 中傳遞過去,包括用戶 ID、用戶 IP、請求 ID 等等重要數據。

總而言之,做好 RequestContext 的基礎信息管理就好像為每個請求打造一個身份證,這個身份證的資訊在整個請求的過程中都可以輕易地被調用、獲取,處理業務邏輯的方法再也不需要定義更多不必要的參數,可以專注於核心邏輯的實作,所有的微服務也都統一用一套定義好的方式獲取 RequestContext,避免出現資訊不一致的狀況,假設需要為身份證件多新增一個欄位,此時也不需修改業務邏輯,只要在 RequestContext 的管理層面做添加即可,這個統一的 Context 管理也同樣為系統的權限控制、日誌紀錄、限流等安全機制提供了可靠的基礎,優勢整理如下:

  1. 程式碼簡化:業務方法不用額外用參數傳入基本信息,專注實現核心邏輯
  2. 基本信息一致性:所有微服務統一使用一套身份證件,用 Http Header 來傳遞
  3. 可擴展性:新增基本信息時不需改業務邏輯,只需在 RequestContext 層面調整
  4. 安全性增強:統一管理基本信息對權限控制、日誌監控、請求限流提供可靠基礎支持

RequestContext 核心結構設計

大部分時候 RequestContext 都會選擇在 API Gateway 就處理完成,所有的外部請求都會經過 API Gateway,因此這當然是一個做信息提取的絕佳位置,在 API Gateway 將 RequestContext 基礎信息都管理好後,會透過 Http Header 傳遞給下游的微服務,微服務僅需從 Header 中獲取需要的信息即可,這樣一來各個服務都能專注在特定的業務邏輯上,不需管理複雜的上下文,最多僅需要生成一個屬於該服務的 TraceId 用於後續日誌監控的需求。

而 RequestContext 的數據結構設計需要考慮通用性擴展性兩個核心原則:

  • 通用性:信息一定要夠基本、通用、識別度高、能辨別請求來源:
    • 如用戶的 ID 用於識別具體操作者
    • Client IP 用於安全控制和地理位置、區域判斷
    • 如果 SaaS 服務可以還有租戶 ID 以作為數據隔離的識別符
    • User-Agent 用於識別客戶端類型
    • 請求時間戳用於時效性控制
  • 擴展性**:**可彈性應對未來可能的需求變化,讓系統可根據業務需求自由添加 RequestContext 信息。

原則上會先在系統裡定義一個 RequestInfo 的 DTO,大致如下:

public class RequestInfo {
    /**
     * 請求ID
     */
    private String requestId;
    /**
     * 請求開始時間
     */
    private Date startTime = new Date();
    private String uri;
}

甚至可以在裡面定義一個用來裝 Http Header 的 Map 結構:

/**
* http client 所需 header
*/
private Map<String, String> headers;

等到有一個來自外部的請求訪問到後端時,另一個 class 就會創建一個 ThreadLocal<RequestInfo> 的物件,作為每個線程的專屬存儲空間,然後開始把前端請求裡面包含的基本信用從 HttpServletRequest 裡面拿出來,接著往這個 RequestInfo 物件裡面塞呀塞,存放到 ThreadLocal 中,之後就可以在任何地方通過靜態方法獲取當前請求的上下文,這就是 RequestContext 的核心精髓,後面一兩天會具體來實作 RequestContext 的基礎管理類到我們前幾天的限流 Lab 內,今天就只是簡單建立一個高層次的理解,我也是第一次花時間好好了解 RequestContext 的設計哲學跟用途,之前都是直接拿前人的東西來用而已,等到後面實作下去我想一定會越來越熟悉這些概念,今天先到這個段落,明天再戰吧!

總結

  • 了解為何需要為系統管理上下文
  • 了解基本信息在微服務間傳遞的挑戰
  • 了解 RequestContext 統一管理的優勢
  • 了解基本的核心架構與高層次理解

上一篇
Day 4 | 限流器 Lab 4 實作:Leaky Bucket&流量整形
下一篇
Day 6 | 限流器 Lab 實作:搭建 Filter 實作 RequestContext 基礎信息管理
系列文
系統設計一招一式:最基本的功練到爛熟就是殺手鐧,從單體架構到分布式系統的 Lab 實作筆記14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言